#include <qpainter.h>
#include <qstyle.h>
#include <qtoolbutton.h>
#include <qvector.h>
#include <qpaintevent>
#include <math.h>
#include "graphdrawer.h"

drawer::drawer(QWidget *parent, Qt::WFlags flags) 
    : QWidget(parent, flags), n_(0)
{ 
    setBackgroundRole(QPalette::Background); 
  setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 
  setFocusPolicy(Qt::StrongFocus); 
  rubberBandIsShown = false; 
  
  zoomInB_ = new QToolButton(this);
  zoomInB_->setText("+");
  zoomInB_->adjustSize(); 
  connect(zoomInB_, SIGNAL(clicked()), this, SLOT(zoomIn())); 
  
  zoomOB_ = new QToolButton(this); 
  zoomOB_->setText("-");
  zoomOB_->adjustSize(); 
  connect(zoomOB_, SIGNAL(clicked()), this, SLOT(zoomOut()));
  setMouseTracking(true);
  setSettings(PlotSettings()); 
}

void drawer::setSettings(const PlotSettings &settings) 
{ 
    zoomStack_.resize(1); 
    zoomStack_[0] = settings;
    curZoom_ = 0; 
    zoomInB_->hide(); 
    zoomOB_->hide(); 
    update(); 
}

void drawer::zoomOut()
{
    if (curZoom_ > 0) 
    { 
        --curZoom_; 
        zoomOB_->setEnabled(curZoom_ > 0); 
        zoomInB_->setEnabled(true); 
        zoomInB_->show(); 
        update(); 
    } 
}

void drawer::zoomIn()
{ 
    if (curZoom_ < zoomStack_.size() - 1) 
    { 
        ++curZoom_; 
        zoomInB_->setEnabled( curZoom_ < zoomStack_.size() - 1); 
        zoomOB_->setEnabled(true); 
        zoomOB_->show(); 
        update();
    } 
}

void drawer::setData(std::pair<double, double> * points, unsigned long long pcount, bool m)
{
    mode = m;
    points_ = points;
    n_ = pcount;
    update();
}

void drawer::importSettings(const PlotSettings &set)
{
    setSettings(set);
}

QSize drawer::minimumSizeHint() const 
{ 
  return QSize(16 * Margin, 4 * Margin); 
}  

QSize drawer::sizeHint() const 
{ 
  return QSize(38 * Margin, 10 * Margin); 
}

void drawer::paintEvent(QPaintEvent * event_) 
{ 
    QPainter * cur = new QPainter;
    cur->begin(this);
    drawGrid_(cur);
    drawPoints_(cur);
    if (rubberBandIsShown) 
    { 
        cur->setPen(Qt::green); 
        cur->drawRect(rubberBandRect.normalized());
    }
    cur->end();
}

void drawer::drawGrid_(QPainter * painter)
{
    QRect rect(Margin, Margin, width() - 2 * Margin, height() - 2 * Margin);
    PlotSettings settings = zoomStack_[curZoom_];
    painter->setPen(Qt::black);
    for (int i = 0; i <= settings.numXTicks; ++i)
    {
        int x = rect.left() + (i * (rect.width() - 1) / settings.numXTicks);
        double label = settings.minX + (i * settings.spanX() / settings.numXTicks);
        painter->drawLine(x, rect.bottom(), x, rect.bottom() + 5);
        painter->drawText(x - 50, rect.bottom() + 5, 100, 15, Qt::AlignHCenter | Qt::AlignTop, QString::number(label));
    }
    for (int j = 0; j <= settings.numYTicks; ++j)
    {
        int y = rect.bottom() - (j * (rect.height() - 1) / settings.numYTicks);
        double label = settings.minY + (j * settings.spanY() / settings.numYTicks);
        painter->drawLine(rect.left() - 5, y, rect.left(), y);
        painter->drawText(rect.left() - Margin, y - 10, Margin - 5, 20, Qt::AlignHCenter | Qt::AlignTop, QString::number(label));
    }
    painter->drawRect(rect);
}

void drawer::drawPoints_(QPainter * painter)
{
    PlotSettings settings = zoomStack_[curZoom_];
    QRect rect(Margin, Margin, width() - 2 * Margin, height() - 2 * Margin);
    painter->setClipRect(rect.x() + 1, rect.y() + 1, rect.width() - 2, rect.height() - 2);
    QVector<QPointF> points;
    for (unsigned long long i = 0; i < n_; ++i)
    {
        double dx_ = points_[i].first - settings.minX;
        double dy = points_[i].second - settings.minY;
        double x = rect.left() + (dx_ * (rect.width() - 1) / settings.spanX());
        double y = rect.bottom() - (dy * (rect.height() - 1) / settings.spanY());
        if (abs(x) < 32768 && abs(y) < 32768)
        {
            points.push_back(QPointF(x, y));
        }
    }
    painter->setPen(Qt::red);
    if (mode)
        painter->drawPoints(points);
    else
    {
        for (size_t i = 1; i < points.size(); ++i)
            painter->drawLine(points[i - 1], points[i]);
    }
}

PlotSettings::PlotSettings() 
{ 
  minX = 0.0; 
  maxX = 4.5; 
  numXTicks = 20; 
  minY = 0.0; 
  maxY = 1.1; 
  numYTicks = 5; 
}

void PlotSettings::scroll(int dx_, int dy) 
{ 
  double stepX = spanX() / numXTicks; 
  minX += dx_ * stepX; 
  maxX += dx_ * stepX; 
  double stepY = spanY() / numYTicks; 
  minY += dy * stepY; 
  maxY += dy * stepY; 
}

void PlotSettings::adjust() 
{ 
  adjustAxis(minX, maxX, numXTicks); 
  adjustAxis(minY, maxY, numYTicks); 
}

void PlotSettings::adjustAxis(double &min, double &max, int &numTicks) 
{ 
  const int MinTicks = 4; 
  double grossStep = (max - min) / MinTicks; 
  double step = pow(10, floor(log10(grossStep))); 
  
  if (5 * step < grossStep) 
    step *= 5; 
  else if (2 * step < grossStep) 
    step *= 2; 
  
  numTicks = (int)(ceil(max / step) - floor(min / step)); 
  min = floor(min / step) * step; 
  max = ceil(max / step) * step; 
}

void drawer::updateRubberBandRegion_() 
{ 
    update();
}

void drawer::wheelEvent(QWheelEvent *event) 
{ 
    int numDegrees = event->delta() / 8; 
    int numTicks = numDegrees / 15; 
  
    if (event->orientation() == Qt::Horizontal) 
    zoomStack_[curZoom_].scroll(numTicks, 0); 
    else 
    zoomStack_[curZoom_].scroll(0, numTicks); 
  
    update(); 
}

void drawer::keyPressEvent(QKeyEvent *event) 
{ 
    switch (event->key()) { 
    case Qt::Key_Plus: 
        zoomIn(); 
        break; 
    case Qt::Key_Minus: 
        zoomOut(); 
        break; 
    case Qt::Key_Left: 
        zoomStack_[curZoom_].scroll(-1, 0); 
        update();      
        break; 
    case Qt::Key_Right: 
        zoomStack_[curZoom_].scroll(+1, 0); 
        update(); 
        break; 
    case Qt::Key_Down: 
        zoomStack_[curZoom_].scroll(0, -1); 
        update(); 
        break; 
    case Qt::Key_Up: 
        zoomStack_[curZoom_].scroll(0, +1); 
        update(); 
        break; 
    default: 
        QWidget::keyPressEvent(event); 
  } 
}

void drawer::resizeEvent(QResizeEvent *) 
{ 
    int x = width() - (zoomInB_->width() + zoomOB_->width() + 10); 
    zoomInB_->move(x, 5); 
    zoomOB_->move(x + zoomInB_->width() + 5, 5); 
    update(); 
}

void drawer::mousePressEvent(QMouseEvent *event) 
{ 
    if (event->button() == Qt::LeftButton) 
    { 
    rubberBandIsShown = true; 
    rubberBandRect.setTopLeft(event->pos()); 
    rubberBandRect.setBottomRight(event->pos()); 
    updateRubberBandRegion_(); 
    setCursor(Qt::CrossCursor); 
  } 
}

void drawer::mouseMoveEvent(QMouseEvent *event) 
{ 
    if (rubberBandIsShown) 
    { 
        updateRubberBandRegion_(); 
        rubberBandRect.setBottomRight(event->pos()); 
        updateRubberBandRegion_(); 
    } 
}

void drawer::mouseReleaseEvent(QMouseEvent *event) 
{ 
  if (event->button() == Qt::LeftButton && rubberBandIsShown) 
  { 
    rubberBandIsShown = false; 
    updateRubberBandRegion_(); 
    rubberBandRect.setBottomRight(event->pos());
    unsetCursor(); 
    
    QRect rect = rubberBandRect.normalized(); 
    if (rect.width() < 4 || rect.height() < 4) 
        return; 
    rect.translate(-Margin, -Margin); 
    
    PlotSettings prevSettings = zoomStack_[curZoom_]; 
    PlotSettings settings; 
    double dx = prevSettings.spanX() / (width() - 2 * Margin); 
    double dy = prevSettings.spanY() / (height() - 2 * Margin); 
    settings.minX = prevSettings.minX + dx * rect.left(); 
    settings.maxX = prevSettings.minX + dx * rect.right(); 
    settings.minY = prevSettings.maxY - dy * rect.bottom(); 
    settings.maxY = prevSettings.maxY - dy * rect.top(); 
    settings.adjust(); 
    
    zoomStack_.resize(curZoom_ + 1); 
    zoomStack_.push_back(settings); 
    zoomIn(); 
  } 
}

void drawer::data(std::pair<double, double> * points, unsigned long long pcount, bool m)
{
    setData(points, pcount, m);
}